﻿using MaxMind.GeoIP2;
using MaxMind.GeoIP2.Exceptions;
using MaxMind.GeoIP2.Responses;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Caching;
namespace DSVISUAL.LocalDNS
{
    public class MaxMindGeoIpService : IDisposable
    {
        static string[] directCities = new string[] { "北京市", "天津市", "上海市", "重庆市" };
        static string[] vendors = new string[] { "移动", "电信", "联通" };
        internal static string GEO_IP_DATABASE_PATH = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data", "GeoLite2-City.mmdb");
        private DatabaseReader _database;
        private DatabaseReader _databaseAsn;
        private DateTime? _databaseLastChecked;
        private DateTime? _databaseLastCheckedAsn;
        public GeoResult ResolveIp(string ip)
        {
            if (String.IsNullOrEmpty(ip) || (!ip.Contains(".") && !ip.Contains(":")))
                return null;

            ip = ip.Trim();
            if (IsPrivateNetwork(ip))
                return null;

            var cacheValue = HttpRuntime.Cache.Get(ip);
            if (cacheValue != null)
            {
                if (cacheValue is string)
                {
                    return null;
                }
                else
                    return cacheValue as GeoResult;
            }
            GeoResult result = null;
            var database = GetDatabase();
            if (database == null)
                return null;

            try
            {
                CityResponse city;
                if (database.TryCity(ip, out city) && city?.Location != null)
                {
                    result = new GeoResult
                    {
                        Latitude = city.Location.Latitude,
                        Longitude = city.Location.Longitude,
                        Country = city.Country.Names.ContainsKey("zh-CN") ? city.Country.Names["zh-CN"] : city.Country.IsoCode,
                        Level1 = city.MostSpecificSubdivision.Names.ContainsKey("zh-CN") ? city.MostSpecificSubdivision.Names["zh-CN"] : city.MostSpecificSubdivision.IsoCode,
                        Locality = city.City.Names.ContainsKey("zh-CN") ? city.City.Names["zh-CN"] : city.City.Name
                    };
                    if (result.Locality != null)
                    {
                        var sep = -1;
                        if ((sep = result.Locality.IndexOf(' ')) != -1)
                        {
                            //河南省镇平县城关镇=ZhenPingXian ChengGuanZhen
                            result.Locality = result.Locality.Substring(0, sep);
                        }
                    }
                }
                //if (result == null)
                //{
                //    var ipcity = db.Database.SqlQuery<IpCity>("SELECT TOP 1 * FROM DBIP_CITY WHERE IP_START>@p0", new SqlParameter("p0", ip)).FirstOrDefault();
                //    if (ipcity != null && isInRange(ip, ipcity.Ip_Start, ipcity.Ip_End))
                //    {
                //        result = new GeoResult();
                //        result.Country = ipcity.Country == "CN" ? "中国" : ipcity.Country;
                //        result.Level1 = ipcity.Region;
                //        result.Locality = ipcity.City;
                //    }
                //}
                if (result != null)
                {
                    HttpRuntime.Cache.Add(ip, result, null, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
                }
                else
                {
                    HttpRuntime.Cache.Add(ip, "NULL", null, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
                }
                return result;
            }
            catch (Exception ex)
            {
                //if (ex is GeoIP2Exception)
                //{
                //    ex.ToExceptionless().Submit();
                //}
                //else
                //{
                //    new Exception("ip2geo fails:" + ip, ex).ToExceptionless().Submit();
                //}
                return null;
            }
        }

        bool isInRange(string ip, string rangeStart, string rangeEnd)
        {
            var source = ip.Split('.').Select(x => int.Parse(x)).Take(2);
            var start = rangeStart.Split('.').Select(x => int.Parse(x)).Take(2);
            var end = rangeEnd.Split('.').Select(x => int.Parse(x)).Take(2);
            return source.SequenceEqual(start) && source.SequenceEqual(end);
        }
        public string ResolveIpAsn(string ip)
        {
            if (String.IsNullOrEmpty(ip) || (!ip.Contains(".") && !ip.Contains(":")))
                return null;

            ip = ip.Trim();
            if (IsPrivateNetwork(ip))
                return null;

            var cacheValue = HttpRuntime.Cache.Get(ip + "_asn") as string;
            if (cacheValue != null)
                return cacheValue;

            string result = null;
            var _databaseAsn = GetDatabaseASN();
            if (_databaseAsn == null)
                return null;

            try
            {
                AsnResponse asn;
                if (_databaseAsn.TryAsn(ip, out asn) && asn?.AutonomousSystemOrganization != null)
                {
                    result = asn.AutonomousSystemOrganization;
                }
                HttpRuntime.Cache.Add(ip + "_asn", result, null, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
                return result;
            }
            catch (Exception ex)
            {
                if (ex is GeoIP2Exception)
                {
                    //ex.ToExceptionless().Submit();
                }
                else
                {
                    //new Exception("ip2geo fails:" + ip, ex).ToExceptionless().Submit();
                }
                return null;
            }
        }
        private DatabaseReader GetDatabase()
        {
            if (_database != null && _databaseLastChecked.HasValue && _databaseLastChecked.Value < DateTime.UtcNow.AddDays(-1))
            {
                _database.Dispose();
                _database = null;
            }

            if (_database != null)
                return _database;

            if (_databaseLastChecked.HasValue && _databaseLastChecked.Value >= DateTime.UtcNow.AddSeconds(-30))
                return null;

            _databaseLastChecked = DateTime.UtcNow;

            using (var stream = new MemoryStream(LocalDNS.Properties.Resources.GeoLite2_City))
                _database = new DatabaseReader(stream);
            return _database;
        }
        bool IsLocalHost(string ip)
        {
            if (String.IsNullOrEmpty(ip))
                return false;

            return String.Equals(ip, "127.0.0.1") || String.Equals(ip, "::1");
        }
        bool IsPrivateNetwork(string ip)
        {
            if (String.IsNullOrEmpty(ip))
                return false;

            if (IsLocalHost(ip))
                return true;

            // 10.0.0.0 – 10.255.255.255 (Class A)
            if (ip.StartsWith("10."))
                return true;

            // 172.16.0.0 – 172.31.255.255 (Class B)
            if (ip.StartsWith("172."))
            {
                for (int range = 16; range < 32; range++)
                {
                    if (ip.StartsWith("172." + range + "."))
                        return true;
                }
            }

            // 192.168.0.0 – 192.168.255.255 (Class C)
            return ip.StartsWith("192.168.");
        }

        private DatabaseReader GetDatabaseASN()
        {
            if (_databaseAsn != null && _databaseLastCheckedAsn.HasValue && _databaseLastCheckedAsn.Value < DateTime.UtcNow.AddDays(-1))
            {
                _databaseAsn.Dispose();
                _databaseAsn = null;
            }

            if (_databaseAsn != null)
                return _databaseAsn;

            if (_databaseLastCheckedAsn.HasValue && _databaseLastCheckedAsn.Value >= DateTime.UtcNow.AddSeconds(-30))
                return null;

            _databaseLastCheckedAsn = DateTime.UtcNow;


            using (var stream = new MemoryStream(DSVISUAL.LocalDNS.Properties.Resources.GeoLite2_City))
                _databaseAsn = new DatabaseReader(stream);

            return _databaseAsn;
        }
        public void Dispose()
        {
            _database?.Dispose();
            _database = null;
            _databaseAsn?.Dispose();
            _databaseAsn = null;
        }
    }
}
